Цель исследования - найти интересные особенности рынка заведений общественного питания Москвы и презентовать полученные результаты, которые в будущем помогут в выборе подходящего инвесторам места.
Ход исследования:
# импорт библиотек
import pandas as pd
import numpy as np
import matplotlib.pyplot as plt
from matplotlib.pyplot import figure
from plotly import graph_objects as go
import plotly.express as px
import seaborn as sns
import os
import re
!pip install geopy
from geopy.distance import geodesic as GD
Collecting geopy
Downloading geopy-2.3.0-py3-none-any.whl (119 kB)
|████████████████████████████████| 119 kB 1.0 MB/s eta 0:00:01
Collecting geographiclib<3,>=1.52
Downloading geographiclib-2.0-py3-none-any.whl (40 kB)
|████████████████████████████████| 40 kB 3.1 MB/s eta 0:00:01
Installing collected packages: geographiclib, geopy
Successfully installed geographiclib-2.0 geopy-2.3.0
# чтение файла с данными и сохранение в df
pth1 = '/datasets/moscow_places.csv'
pth2 = '/moscow_places.csv'
if os.path.exists(pth1):
df = pd.read_csv(pth1)
elif os.path.exists(pth2):
df = pd.read_csv(pth2)
else:
print('FilePathError')
# выведем первые пять строк датафрейма
df.head()
| name | category | address | district | hours | lat | lng | rating | price | avg_bill | middle_avg_bill | middle_coffee_cup | chain | seats | |
|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|
| 0 | WoWфли | кафе | Москва, улица Дыбенко, 7/1 | Северный административный округ | ежедневно, 10:00–22:00 | 55.878494 | 37.478860 | 5.0 | NaN | NaN | NaN | NaN | 0 | NaN |
| 1 | Четыре комнаты | ресторан | Москва, улица Дыбенко, 36, корп. 1 | Северный административный округ | ежедневно, 10:00–22:00 | 55.875801 | 37.484479 | 4.5 | выше среднего | Средний счёт:1500–1600 ₽ | 1550.0 | NaN | 0 | 4.0 |
| 2 | Хазри | кафе | Москва, Клязьминская улица, 15 | Северный административный округ | пн-чт 11:00–02:00; пт,сб 11:00–05:00; вс 11:00... | 55.889146 | 37.525901 | 4.6 | средние | Средний счёт:от 1000 ₽ | 1000.0 | NaN | 0 | 45.0 |
| 3 | Dormouse Coffee Shop | кофейня | Москва, улица Маршала Федоренко, 12 | Северный административный округ | ежедневно, 09:00–22:00 | 55.881608 | 37.488860 | 5.0 | NaN | Цена чашки капучино:155–185 ₽ | NaN | 170.0 | 0 | NaN |
| 4 | Иль Марко | пиццерия | Москва, Правобережная улица, 1Б | Северный административный округ | ежедневно, 10:00–22:00 | 55.881166 | 37.449357 | 5.0 | средние | Средний счёт:400–600 ₽ | 500.0 | NaN | 1 | 148.0 |
# выведем информацию о датафрейме
df.info()
<class 'pandas.core.frame.DataFrame'> RangeIndex: 8406 entries, 0 to 8405 Data columns (total 14 columns): # Column Non-Null Count Dtype --- ------ -------------- ----- 0 name 8406 non-null object 1 category 8406 non-null object 2 address 8406 non-null object 3 district 8406 non-null object 4 hours 7870 non-null object 5 lat 8406 non-null float64 6 lng 8406 non-null float64 7 rating 8406 non-null float64 8 price 3315 non-null object 9 avg_bill 3816 non-null object 10 middle_avg_bill 3149 non-null float64 11 middle_coffee_cup 535 non-null float64 12 chain 8406 non-null int64 13 seats 4795 non-null float64 dtypes: float64(6), int64(1), object(7) memory usage: 919.5+ KB
В таблице 'df' 14 столбцов, 8406 строк. В каждой строке информация об одном завелении. Типы данных float64(6), int64(1), object(7).
#Построение общей гистограммы для всех числовых столбцов таблицы
df.hist(figsize=(15, 20))
plt.show()
На гистограммах видны выбросы в значениях некоторых столбцов. В middle_avg_bill есть значения больше 30 000, в middle_coffee_cup есть значение 1500, а в столбце seats есть значения до 1250.
Выводы после обзора данных:
Предварительно можно утверждать, что данных достаточно для анализа. Но есть пропуски и выбросы.
План предобработки данных:
# проверяем дубликаты и пропуски в таблице df
print('Количество дубликатов в таблице df - ',df.duplicated().sum())
print('Количество пропусков в таблице df')
print(df.isna().sum().sort_values(ascending=False))
Количество дубликатов в таблице df - 0 Количество пропусков в таблице df middle_coffee_cup 7871 middle_avg_bill 5257 price 5091 avg_bill 4590 seats 3611 hours 536 name 0 category 0 address 0 district 0 lat 0 lng 0 rating 0 chain 0 dtype: int64
# приводим к нижниму регистру
df['address'] = df['address'].str.lower()
df['name'] = df['name'].str.lower()
Привели значения в столбце в адресом и названием заведения к нижниму регистру, чтобы найти неявные дубликаты. Будем считать неуникальными записи, где название и адрес заведения совпадают.
# удаляем строки, где название и адрес одинаковые
key = ['name', 'address']
df_dedupped2 = df.drop_duplicates(subset=key)
print("Количество строк в исходном датасете - ",df['name'].count())
print("Количество строк после удаления неявных дубликатов - ",df_dedupped2['name'].count())
df = df_dedupped2
Количество строк в исходном датасете - 8406 Количество строк после удаления неявных дубликатов - 8402
Удалили 4 неявных дубликата.
# строим тепловую карту пропущенных значений
fig, ax = plt.subplots()
fig.set_figheight(10) # изменение по высоте
fig.set_figwidth(15) # изменение по ширине
colours = ['#39E639', '#FF4040']
ax = sns.heatmap(df.isnull(), cmap=sns.color_palette(colours))
ax.set_title('Тепловая карта пропущенных значений, красный - пропущенные данные, зеленый - не пропущенные ')
ax.set_xlabel('Столбцы')
ax.set_ylabel('Строки')
plt.show()
# выводим список столбцов с количеством пропусков в процентах
for col in df.columns:
pct_missing = np.mean(df[col].isnull())
print('{} - {}%'.format(col, round(pct_missing*100)))
name - 0% category - 0% address - 0% district - 0% hours - 6% lat - 0% lng - 0% rating - 0% price - 61% avg_bill - 55% middle_avg_bill - 63% middle_coffee_cup - 94% chain - 0% seats - 43%
Явных дубликатов обнаружено не было. Было обнаружено и удалено 4 неявных дубликата.
Обнаружены пропуски в столбцах с данными, которые добавляют пользователи:
Также обнаружены пропуски в стобцах, заполненых автоматически:
Пропуски в данных, которые добавляют пользователем, могут быть из-за того, что пользователи не указывают нужные данные или из-за технических ошибок, связанных с обработкой этих данных.
Пропуски в столбцах заполненых автоматически появляются из-за того, что в они заполняются исходя из значений в столбце avg_bill и если в столбце avg_bill нет необходимой информации, то появляется пропуск.
Столбец middle_coffee_cup имеет 94% пропущенных значений, это связано с тем, что данный сталбец актуален только для заведений в категории "кофейня", для заведений из остальных категорий там пропуск.
Вывод
Поскольку пропуски содержатся только в столбцах с данными, которые заполняют пользователи, или с данными заполненными на их основе, то заменить эти пропуски мы не сможем. Оставим эти пропуски и проведем анализ с ними.
#выводим диаграмму boxplot
figure(figsize=(15, 6), dpi=80)
df.boxplot()
plt.show()
На графике видно, что у столбца middle_avg_bill есть сильные выбросы вплоть до 35 000. Но остальные столбцы видно плохо, приблизим график, чтобы лучше их рассмотреть.
from matplotlib.pyplot import figure
figure(figsize=(15, 6), dpi=80)
plt.ylim(-100, 2000)
df.boxplot()
plt.show()
На этом графике видно, что у middle_coffee_cup есть выброс со значением больше 1500, а у столбца seats есть выбросы больше 1250. Скорее всего это не правильно заполненные данные и они могут повлиять на анализ данных. Поскольку данных мы будем работать с агрегированными данными, то удалять эти данные не стоит, следует брать медианные значения а не средние, так как на медиану меньше влияют выбросы.
# пишем функцию для определения названия улицы в столце с адресом
def street(x):
s = re.split(',', x)
words = ['улица','проезд','шоссе','километр','заказник','парк','проспект','бульвар','переулок','квартал','аллея', 'сад','сквер','линия','набережная','музей','площадь','просек','тупик','кольцо','памятник','мост','тоннель','пр-т','ул.','Сумская']
chk_pat = '(?:{})'.format('|'.join(words))
for i in s:
if bool(re.search(chk_pat, i, flags=re.I)):
return i
return None
# пишем функцию для определения того, что заведение работает ежедневно и круглосуточно
def is_24_per_7(x):
if x == 'ежедневно, круглосуточно':
return True
elif not isinstance(x, str):
return None
else:
return False
#добавляем столбец с обозначением, что заведение работает ежедневно и круглосуточно
df['is_24/7'] = df['hours'].apply(is_24_per_7)
#добавляем столбец с названиями улиц из столбца с адресом
df['street'] = df['address'].apply(street)
В ходе предобработки данных мы проверили данные на дубликаты, пропущенные значени и выбросы, добавили столбец с обозначением, что заведение работает ежедневно и круглосуточно и столбец с названиями улиц из столбца с адресом.
Явных дубликатов обнаружено не было. Было обнаружено и удалено 4 неявных дубликата.
Были обнаружены пропуски в столбцах:
Пропуски были обнаруженны только в столбцах с данными, которые заполняют пользователи, или с данными заполненными на их основе. Заменить такие пропуски мы не сможем. Приняли решение оставить эти пропуски и провести анализ с ними.
Были обнаружены аномальные значения в столбцах:
Скорее всего это не правильно заполненные данные, которые могут повлиять на анализ. Но так как мы будем оперировать аггрегированными данными, то мы решили не отбрасывать данные, а использовать медиану для получения средних значений, потому что на медиану не так сильно влияют выбросы.
Данные готовы к анализу.
# выведем таблицу распределения количества заведений по категориям
sample = df.groupby('category', as_index=False).agg({'name': 'count'}).sort_values(by = 'name', ascending=False)
sample['%'] = round(sample['name'] / sample['name'].sum() * 100)
display(sample)
| category | name | % | |
|---|---|---|---|
| 3 | кафе | 2376 | 28.0 |
| 6 | ресторан | 2042 | 24.0 |
| 4 | кофейня | 1413 | 17.0 |
| 0 | бар,паб | 764 | 9.0 |
| 5 | пиццерия | 633 | 8.0 |
| 2 | быстрое питание | 603 | 7.0 |
| 7 | столовая | 315 | 4.0 |
| 1 | булочная | 256 | 3.0 |
В данных представлены 8 категорий заведений. Чтобы подробнее оценить распределение, построим круговую диаграмму.
# строим график распределения заведений по категориям
fig, ax = plt.subplots()
fig.set_figheight(5) # изменение по высоте
fig.set_figwidth(15) # изменение по ширине
ax = sns.barplot(x= "category", y="name", data=sample)
ax.set_title('Распределения заведений по категориям')
ax.set_xlabel('Категории')
ax.set_ylabel('Количество заведений')
plt.show()
Как видно на графике, большая часть заведений находится в категориях "кафе", "ресторан", "кофейня", они занимают большую часть всех заведений в Москве. Меньше всего булочных и столовых.
Для анализа количества посадочных мест сгруппируем таблицу с медианным количеством мест на заведение по категориям. Используем медиану, а не среднее из-за того что в данных по посадочным местам есть сильные выбросы, которые мы заметили на этапе обзора и предобработки данных.
# выведем таблицу с медианным количеством мест в заведениях по категориям.
sample = df.groupby('category', as_index=False).agg({'seats': 'median'}).sort_values(by = 'seats', ascending=False)
display(sample)
| category | seats | |
|---|---|---|
| 6 | ресторан | 86.0 |
| 0 | бар,паб | 82.0 |
| 4 | кофейня | 80.0 |
| 7 | столовая | 75.5 |
| 2 | быстрое питание | 65.0 |
| 3 | кафе | 60.0 |
| 5 | пиццерия | 55.0 |
| 1 | булочная | 50.0 |
Визуализуруем данные для анализа.
# строим график среднего количества посадочных мест в заведениях по категориям
fig, ax = plt.subplots()
fig.set_figheight(5) # изменение по высоте
fig.set_figwidth(15) # изменение по ширине
ax = sns.barplot(x= "category", y="seats", data=sample)
ax.set_title('Количество посадочных мест на заведение')
ax.set_xlabel('Категории')
ax.set_ylabel('Количество посадочных мест')
plt.show()
Больше всего посадочных мест в заведениях в категории "ресторан", "бар,паб" и "кофейня", около 80 на заведение. Меньше всего в пиццериях и булочных, около 50.
# выведем таблицу с количеством сетевых и не сетевых заведений
sample = df.groupby('chain', as_index=False).agg({'name': 'count'}).sort_values(by = 'name', ascending=False).replace({0: 'несетевые', 1: 'сетевые'})
display(sample)
| chain | name | |
|---|---|---|
| 0 | несетевые | 5199 |
| 1 | сетевые | 3203 |
# строим круговую диаграмму распределения количества заведений по категориям
fig = go.Figure(data=[go.Pie(labels=sample['chain'], values=sample['name'])])
fig.update_layout(title="Распределение количества сетевых и несетевых заведений")
fig.show()
На диаграмме видно, что несетевых заведений существенно больше, чем сетевых.
Рассмотрим категории сетевых заведений
# сформеруем сводную таблицу с долей сетевых компаний в каждой категории
chain_cat = df.pivot_table(index='category', columns='chain', values='name', aggfunc='count')
chain_cat['% chain'] = chain_cat[1] / (chain_cat[0] + chain_cat[1])
chain_cat = chain_cat.sort_values(by='% chain', ascending=False).reset_index()
chain_cat.rename(columns = {0:'not chain', 1:'chain'}, inplace = True )
chain_cat
| chain | category | not chain | chain | % chain |
|---|---|---|---|---|
| 0 | булочная | 99 | 157 | 0.613281 |
| 1 | пиццерия | 303 | 330 | 0.521327 |
| 2 | кофейня | 693 | 720 | 0.509554 |
| 3 | быстрое питание | 371 | 232 | 0.384743 |
| 4 | ресторан | 1313 | 729 | 0.357003 |
| 5 | кафе | 1597 | 779 | 0.327862 |
| 6 | столовая | 227 | 88 | 0.279365 |
| 7 | бар,паб | 596 | 168 | 0.219895 |
# строим график долей сетевых заведений по категориям
fig, ax = plt.subplots()
fig.set_figheight(5) # изменение по высоте
fig.set_figwidth(15) # изменение по ширине
ax = sns.barplot(x= "category", y='% chain', data=chain_cat)
ax.set_title('Доля сетевых заведений по категориям')
ax.set_xlabel('Категории')
ax.set_ylabel('Доля сетевых заведений')
plt.show()
Больше всего сетевых заведений в категории "булочная" около 60%. Далее идут пиццерии и кофейни, в этих категориях доля сетевых заведений около 50%. Меньше всего сетевых заведений в категории "бар,паб" около 20%.
# сгрупперуем таблицу и выведем топ-15 популяных сетей в Москве
top_name = (df.query('chain == 1')
.groupby('name', as_index=False)
.agg({'address': 'count'})
.sort_values(by = 'address', ascending=False)
.head(15))
top_name
| name | address | |
|---|---|---|
| 729 | шоколадница | 120 |
| 335 | домино'с пицца | 76 |
| 331 | додо пицца | 74 |
| 146 | one price coffee | 71 |
| 742 | яндекс лавка | 69 |
| 58 | cofix | 65 |
| 168 | prime | 50 |
| 664 | хинкальная | 44 |
| 409 | кофепорт | 42 |
| 418 | кулинарная лавка братьев караваевых | 39 |
| 628 | теремок | 38 |
| 683 | чайхана | 37 |
| 39 | cofefest | 32 |
| 267 | буханка | 32 |
| 477 | му-му | 27 |
# строим график долей сетевых заведений по категориям
fig, ax = plt.subplots()
fig.set_figheight(10) # изменение по высоте
fig.set_figwidth(10) # изменение по ширине
ax = sns.barplot(x= "address", y='name', data=top_name)
ax.set_title('Топ-15 популярных сетей в Москве')
ax.set_xlabel('Количество точек сети')
ax.set_ylabel('Название')
plt.show()
В топ-15 популярных сетей в Москве первые 10 мест заняли франшизы. На первом месте находится сеть Шоколадница с более чем 120 заведениями.
Последние 5 мест занимают частные сетевые заведения, на первом месте среди них Теремок, чуть меньше 40 заведений по всей Москве.
# выводим общее количество заведений по районам
district_total = df.pivot_table(index='district', values='name', aggfunc='count').reset_index()
district_total
| district | name | |
|---|---|---|
| 0 | Восточный административный округ | 798 |
| 1 | Западный административный округ | 850 |
| 2 | Северный административный округ | 898 |
| 3 | Северо-Восточный административный округ | 890 |
| 4 | Северо-Западный административный округ | 409 |
| 5 | Центральный административный округ | 2242 |
| 6 | Юго-Восточный административный округ | 714 |
| 7 | Юго-Западный административный округ | 709 |
| 8 | Южный административный округ | 892 |
# импортируем карту и хороплет
from folium import Map, Choropleth
# загружаем JSON-файл с границами округов Москвы
state_geo = '/datasets/admin_level_geomap.geojson'
# moscow_lat - широта центра Москвы, moscow_lng - долгота центра Москвы
moscow_lat, moscow_lng = 55.751244, 37.618423
# создаём карту Москвы
m = Map(location=[moscow_lat, moscow_lng], zoom_start=10)
# создаём хороплет с помощью конструктора Choropleth и добавляем его на карту
Choropleth(
geo_data=state_geo,
data=district_total,
columns=['district', 'name'],
key_on='feature.name',
fill_color='YlGn',
fill_opacity=0.8,
legend_name='Количество заведений по районам',
).add_to(m)
# выводим карту
m
# выводим количество заведений каждой категории по районам
district = df.pivot_table(index='district', columns='category', values='name', aggfunc='count')
district['total'] = district.sum(axis=1)
district = district.sort_values(by = 'total')
district = district.drop(columns=['total'])
district
| category | бар,паб | булочная | быстрое питание | кафе | кофейня | пиццерия | ресторан | столовая |
|---|---|---|---|---|---|---|---|---|
| district | ||||||||
| Северо-Западный административный округ | 23 | 12 | 30 | 115 | 62 | 40 | 109 | 18 |
| Юго-Западный административный округ | 38 | 27 | 61 | 238 | 96 | 64 | 168 | 17 |
| Юго-Восточный административный округ | 38 | 13 | 67 | 282 | 89 | 55 | 145 | 25 |
| Восточный административный округ | 53 | 25 | 71 | 272 | 105 | 72 | 160 | 40 |
| Западный административный округ | 50 | 37 | 62 | 238 | 150 | 71 | 218 | 24 |
| Северо-Восточный административный округ | 62 | 28 | 82 | 269 | 159 | 68 | 182 | 40 |
| Южный административный округ | 68 | 25 | 85 | 264 | 131 | 73 | 202 | 44 |
| Северный административный округ | 68 | 39 | 58 | 234 | 193 | 77 | 188 | 41 |
| Центральный административный округ | 364 | 50 | 87 | 464 | 428 | 113 | 670 | 66 |
# строим столбчатую диграмму количества заведений по районам
district.plot(kind='barh',
stacked=True,
figsize=(15, 10),
xlabel="Район",
ylabel="Количество заведений",
title='Количество заведений по районам',
grid=True)
plt.show()
Как видно на графике, больше всего заведений находится в Центральном административном округе, из них большая часть это рестораны, кафе, кофейни и бары. В ЦАО гораздо больше баров, относительно других категорий, по сравнению с остальными районами. А вот заведений быстрого питания наоборот меньше, относительно других категорий, по сравнению с остальными районами.
Из-за большого количества заведений в ЦАО, на графике плохо видо остальные районы. Ограничим ось x до значении 1200, чтоб рассмотреть другие районы.
# строим столбчатую диграмму количества заведений по районам
district.plot(kind='barh',
stacked=True,
figsize=(15, 10),
xlabel="Район",
ylabel="Количество заведений",
title='Количество заведений по районам',
grid=True, xlim=(0, 1100))
plt.show()
Меньше всего заведений в Северо-Западном административном округе. Распределение категорий в разнах районах за исключением ЦАО примерно одинаковое. Большую часть занимаю кафе, рестораны и кофейни.
sample = df.groupby('category', as_index=False).agg({'rating':'mean'}).sort_values(by='rating', ascending=False)
sample
| category | rating | |
|---|---|---|
| 0 | бар,паб | 4.387696 |
| 5 | пиццерия | 4.301264 |
| 6 | ресторан | 4.290402 |
| 4 | кофейня | 4.277282 |
| 1 | булочная | 4.268359 |
| 7 | столовая | 4.211429 |
| 3 | кафе | 4.124285 |
| 2 | быстрое питание | 4.050249 |
# выводим диаграмму среднего рейтинга заведений по категориям
fig, ax = plt.subplots()
fig.set_figheight(5) # изменение по высоте
fig.set_figwidth(15) # изменение по ширине
ax = sns.barplot(x="category", y="rating", data=sample)
Как видно на графике, средний рейтинг по категориям не сильно отличается в разных категориях. Самый низкий средний рейтинг в категории "быстрое питание" - около 4, самый высокий в категории "бар,паб" - около 4,5.
Построим фоновую картограмму (хороплет) со средним рейтингом заведений каждого района.
sample = df.groupby('district', as_index=False).agg({'rating':'mean'}).sort_values(by='rating', ascending=False)
sample
| district | rating | |
|---|---|---|
| 5 | Центральный административный округ | 4.377520 |
| 2 | Северный административный округ | 4.240980 |
| 4 | Северо-Западный административный округ | 4.208802 |
| 8 | Южный административный округ | 4.184417 |
| 1 | Западный административный округ | 4.181647 |
| 0 | Восточный административный округ | 4.174185 |
| 7 | Юго-Западный административный округ | 4.172920 |
| 3 | Северо-Восточный административный округ | 4.147978 |
| 6 | Юго-Восточный административный округ | 4.101120 |
# импортируем карту и хороплет
from folium import Map, Choropleth
# загружаем JSON-файл с границами округов Москвы
state_geo = '/datasets/admin_level_geomap.geojson'
# moscow_lat - широта центра Москвы, moscow_lng - долгота центра Москвы
moscow_lat, moscow_lng = 55.751244, 37.618423
# создаём карту Москвы
m = Map(location=[moscow_lat, moscow_lng], zoom_start=10)
# создаём хороплет с помощью конструктора Choropleth и добавляем его на карту
Choropleth(
geo_data=state_geo,
data=sample,
columns=['district', 'rating'],
key_on='feature.name',
fill_color='YlGn',
fill_opacity=0.8,
legend_name='Средний рейтинг заведений по районам',
).add_to(m)
# выводим карту
m
На картограмме явно выделяется Центральный административный округ, там самый высокий средний рейтинг заведений. Хуже всего с рейтингом обстоят дела в Юго-Восточном административном округе.
Отобразим все заведения на карте
# импортируем карту и маркер
from folium import Map, Marker
# импортируем кластер
from folium.plugins import MarkerCluster
# moscow_lat - широта центра Москвы, moscow_lng - долгота центра Москвы
moscow_lat, moscow_lng = 55.751244, 37.618423
# создаём карту Москвы
m = Map(location=[moscow_lat, moscow_lng], zoom_start=10)
# создаём пустой кластер, добавляем его на карту
marker_cluster = MarkerCluster().add_to(m)
# пишем функцию, которая принимает строку датафрейма,
# создаёт маркер в текущей точке и добавляет его в кластер marker_cluster
def create_clusters(row):
Marker(
[row['lat'], row['lng']],
popup=f"{row['name']} {row['rating']}",
).add_to(marker_cluster)
# применяем функцию create_clusters() к каждой строке датафрейма
df.apply(create_clusters, axis=1)
# выводим карту
m
Отобразили все заведения в датасете с помощью кластеров на карте.
# выведем таблицу с топ-15 улиц по количеству заведений
street_total = df.pivot_table(index='street', values='name', aggfunc='count').reset_index().sort_values(by = 'name', ascending=False)
street_total.head(15)
| street | name | |
|---|---|---|
| 863 | проспект мира | 183 |
| 867 | профсоюзная улица | 122 |
| 860 | проспект вернадского | 108 |
| 560 | ленинский проспект | 107 |
| 558 | ленинградский проспект | 95 |
| 405 | дмитровское шоссе | 88 |
| 487 | каширское шоссе | 77 |
| 328 | варшавское шоссе | 76 |
| 559 | ленинградское шоссе | 70 |
| 582 | люблинская улица | 60 |
| 1107 | улица вавилова | 55 |
| 550 | кутузовский проспект | 54 |
| 1264 | улица миклухо-маклая | 49 |
| 875 | пятницкая улица | 48 |
| 195 | алтуфьевское шоссе | 47 |
# собираем заведения, которые находятся на топ-15 улиц по количеству заведений
street = df.pivot_table(index='street', columns='category', values='name', aggfunc='count').fillna(0)
street['total'] = street.sum(axis=1)
street = street.sort_values(by = 'total')
street = street.drop(columns=['total']).tail(15)
# выводим диаграмму с топ-15 улиц по количеству заведений
street.plot(kind='barh',
stacked=True,
figsize=(15, 10),
xlabel='Название улицы',
ylabel="Количество заведений",
title='Топ-15 улиц по количеству заведений в Москве',
grid=True)
plt.show()
Больше всего заведений на проспекте Мира, целых 183. Замыкает топ-15 ул.Миклухо-Маклая с 47 заведениями. Распределение категорий в на топ-15 улицах примерно одинаковое. Большую часть занимают кафе, рестораны и кофейни. На графике можно заметить, что есть несколько улиц вообще без булочных. И несколько улиц на которых очень мало заведений быстрого питания, относительно других категорий, по сравнению с другими улицами.
# соберем в таблицу заведения, которые одни на своей улице
one_street = df[df['street'].isin(street_total.query('name == 1')['street'].tolist())]
print('Количество улиц, на которых находится только один объект общепита - ',one_street['street'].count())
print('Доля таких улиц от общего числа улиц {0:.1%}'.format(one_street['name'].count() / street_total['name'].count()))
Количество улиц, на которых находится только один объект общепита - 478 Доля таких улиц от общего числа улиц 32.4%
На 32% улиц есть только одно заведение общественного питания.
# выводим таблицу распределения таких улиц по районам
sample = one_street.groupby('district', as_index=False).agg({'address': 'count'}).sort_values(by = 'address', ascending=False)
display(sample)
| district | address | |
|---|---|---|
| 5 | Центральный административный округ | 144 |
| 0 | Восточный административный округ | 57 |
| 3 | Северо-Восточный административный округ | 57 |
| 2 | Северный административный округ | 52 |
| 8 | Южный административный округ | 44 |
| 1 | Западный административный округ | 43 |
| 6 | Юго-Восточный административный округ | 42 |
| 4 | Северо-Западный административный округ | 20 |
| 7 | Юго-Западный административный округ | 19 |
# импортируем карту и хороплет
from folium import Map, Choropleth
# загружаем JSON-файл с границами округов Москвы
state_geo = '/datasets/admin_level_geomap.geojson'
# moscow_lat - широта центра Москвы, moscow_lng - долгота центра Москвы
moscow_lat, moscow_lng = 55.751244, 37.618423
# создаём карту Москвы
m = Map(location=[moscow_lat, moscow_lng], zoom_start=10)
# создаём хороплет с помощью конструктора Choropleth и добавляем его на карту
Choropleth(
geo_data=state_geo,
data=sample,
columns=['district', 'address'],
key_on='feature.name',
fill_color='YlGn',
fill_opacity=0.8,
legend_name='Количество единственных заведений на улице по районам',
).add_to(m)
# выводим карту
m
Больше всего таких улиц в Центральном административном округе, меньше всего таких улиц в Северо-Западном и Юго-Западном административных округах.
Построим фоновую картограмму (хороплет) со медианным чеком заведений каждого района
bill_district = df.groupby('district', as_index=False).agg({'middle_avg_bill': 'median'}).sort_values(by = 'middle_avg_bill', ascending=False)
bill_district
| district | middle_avg_bill | |
|---|---|---|
| 1 | Западный административный округ | 1000.0 |
| 5 | Центральный административный округ | 1000.0 |
| 4 | Северо-Западный административный округ | 700.0 |
| 2 | Северный административный округ | 650.0 |
| 7 | Юго-Западный административный округ | 600.0 |
| 0 | Восточный административный округ | 575.0 |
| 3 | Северо-Восточный административный округ | 500.0 |
| 8 | Южный административный округ | 500.0 |
| 6 | Юго-Восточный административный округ | 450.0 |
# строим график долей сетевых заведений по категориям
fig, ax = plt.subplots()
fig.set_figheight(10) # изменение по высоте
fig.set_figwidth(10) # изменение по ширине
ax = sns.barplot(x= "middle_avg_bill", y='district', data=bill_district)
ax.set_title('Средний чек в заведения по районам')
ax.set_xlabel('Средний чек')
ax.set_ylabel('Район')
plt.show()
# импортируем карту и хороплет
from folium import Map, Choropleth
# загружаем JSON-файл с границами округов Москвы
state_geo = '/datasets/admin_level_geomap.geojson'
# moscow_lat - широта центра Москвы, moscow_lng - долгота центра Москвы
moscow_lat, moscow_lng = 55.751244, 37.618423
# создаём карту Москвы
m = Map(location=[moscow_lat, moscow_lng], zoom_start=10)
# создаём хороплет с помощью конструктора Choropleth и добавляем его на карту
Choropleth(
geo_data=state_geo,
data=bill_district,
columns=['district', 'middle_avg_bill'],
key_on='feature.name',
fill_color='YlGn',
fill_opacity=0.8,
legend_name='Медианный средний чек заведений по районам',
).add_to(m)
# выводим карту
m
Самый высокий средний чек в Центральном и Западном административных округах, самый низкий в Северо-Восточном, Южном и Юго-Восточном административных округах.
Исследуем зависимость между ценами и расстоянием до центра
#создадим столбец с расстоянием до центра москвы в километрах
df['distance_center_km'] = df.apply(lambda row: round(GD((55.751244, 37.618423), (row['lat'],row['lng'])).km), axis=1)
# сгрупперуем данные по расстоянию до центра москвы, медианному среднему чеку и медианной средней цене чашки капучино
sample = df.groupby('distance_center_km', as_index=False).agg({'middle_avg_bill': 'median'})
sample
| distance_center_km | middle_avg_bill | |
|---|---|---|
| 0 | 0 | 1175.0 |
| 1 | 1 | 1250.0 |
| 2 | 2 | 1250.0 |
| 3 | 3 | 850.0 |
| 4 | 4 | 825.0 |
| 5 | 5 | 650.0 |
| 6 | 6 | 512.5 |
| 7 | 7 | 470.0 |
| 8 | 8 | 685.0 |
| 9 | 9 | 700.0 |
| 10 | 10 | 600.0 |
| 11 | 11 | 650.0 |
| 12 | 12 | 625.0 |
| 13 | 13 | 550.0 |
| 14 | 14 | 550.0 |
| 15 | 15 | 526.5 |
| 16 | 16 | 600.0 |
| 17 | 17 | 400.0 |
| 18 | 18 | 500.0 |
| 19 | 19 | 750.0 |
| 20 | 20 | NaN |
# выводим диаграмму зависимости между средним чеком и расстоянием до центра
fig, ax = plt.subplots()
ax = sns.regplot(x='distance_center_km', y='middle_avg_bill', data=sample)
ax.set_title('Зависимость между средним чеком и расстоянием до центра')
ax.set_xlabel('Расстояние, км')
ax.set_ylabel('Средний чек')
plt.show()
На графике видно, что чем дальше от центра, тем ниже средний чек.
# выводим коэффициент корреляции пирсона для более точной оценки
print(sample.corr())
distance_center_km middle_avg_bill distance_center_km 1.000000 -0.712728 middle_avg_bill -0.712728 1.000000
Корреляция среднего чека и расстояния до центра заметная, можно утверждать, что чем дальше от центра, тем ниже средний чек. Однако стоит напомнить, что средний чек заполнен только у 45% заведений.
Мы проанализировали датасет с заведениями общественного питания Москвы, составленный на основе данных сервисов Яндекс Карты и Яндекс Бизнес на лето 2022 года. Выявили наиболее распостраненные категории заведений, количество посадочных мест на одно заведение, самые большие сети и их распределение по округам, а также средний рейтинг, средний чек по разным округам.
Данные показывают, что в Москве есть 8 категорий заведений общественного питания, при этом кафе, рестораны и кофейни являются наиболее распространенными, составляя почти 70% всех заведений.
Наибольшее количество посадочных мест на одно заведение находится в ресторанах, барах/пабах и кофейнях, около 80 мест на одно заведение, в то время как в пиццериях и булочных мест меньше, около 50.
Большая часть заведений не сетевые - 62%. Больше всего сетевых заведений в категории "булочная" около 60%. Далее идут пиццерии и кофейни, в этих категориях доля сетевых заведений около 50%. Меньше всего сетевых заведений в категории "бар,паб" около 20%.
В топ-15 популярных сетей в Москве доминируют франшизы, при этом самой большой является сеть "Шоколадница" с более чем 120 заведениями. Частные сетевые заведения занимают последние 5 мест, при этом самой большой среди них является "Теремок" с чуть менее чем 40 заведениями по всей Москве.
Большинство заведений находится в Центральном административном округе, при этом большинство из них являются ресторанами, кафе, кофейнями и барами. А вот количество заведений быстрого питания в ЦАО ниже, чем в других категориях.
Самый высокий средний рейтинг заведений находится в Центральном административном округе, а самый низкий - в Юго-Восточном административном округе.
Из топ-15 улиц по количеству заведений, больше всего заведений находится на проспекте Мира, целых 183.
На 32% улиц есть только одно заведение общественного питания, при этом больше всего таких улиц находится в Центральном административном округе, а меньше всего - в Северо-Западном и Юго-Западном административных округах.
Самый высокий средний чек находится в Центральном и Западном административных округах, а самый низкий - в Юго-Восточном, Южном и Северо-Восточном административных округах.
Самая высокая медианная цена за чашку капучино в заведениях находится в Юго-Западном и Центральном административных округах, а самая низкая - в Юго-Восточном и Восточном административных округах.
Есть заметная корреляция между средним чеком и расстоянием от центра, чем ближе заведение к центру города, тем больше средний чек.
# выводим количество кофеен в датасете
coffee_house = df.query("category == 'кофейня'")
print("Количество кофеен в датасете - ",coffee_house['name'].count())
Количество кофеен в датасете - 1413
# выводим таблицу с количеством кофеен по районам
coffee_house_district = (coffee_house.groupby('district', as_index=False)
.agg({'name':'count'})
.sort_values(by = 'name', ascending=False))
coffee_house_district['%'] = round((coffee_house_district['name'] / coffee_house_district['name'].sum())*100,2)
coffee_house_district
| district | name | % | |
|---|---|---|---|
| 5 | Центральный административный округ | 428 | 30.29 |
| 2 | Северный административный округ | 193 | 13.66 |
| 3 | Северо-Восточный административный округ | 159 | 11.25 |
| 1 | Западный административный округ | 150 | 10.62 |
| 8 | Южный административный округ | 131 | 9.27 |
| 0 | Восточный административный округ | 105 | 7.43 |
| 7 | Юго-Западный административный округ | 96 | 6.79 |
| 6 | Юго-Восточный административный округ | 89 | 6.30 |
| 4 | Северо-Западный административный округ | 62 | 4.39 |
# импортируем карту и хороплет
from folium import Map, Choropleth
# загружаем JSON-файл с границами округов Москвы
state_geo = '/datasets/admin_level_geomap.geojson'
# moscow_lat - широта центра Москвы, moscow_lng - долгота центра Москвы
moscow_lat, moscow_lng = 55.751244, 37.618423
# создаём карту Москвы
m = Map(location=[moscow_lat, moscow_lng], zoom_start=10)
# создаём хороплет с помощью конструктора Choropleth и добавляем его на карту
Choropleth(
geo_data=state_geo,
data=coffee_house_district,
columns=['district', 'name'],
key_on='feature.name',
fill_color='YlGn',
fill_opacity=0.8,
legend_name='Количество кофеен по районам',
).add_to(m)
# выводим карту
m
Больше всего кофеен находится в Центральном административном округе, меньше всего в Северо-Заподном административном округе.
# выведем таблицу с количеством круглосуточных и не круглосуточных кофеен
sample = coffee_house.groupby('is_24/7', as_index=False).agg({'name': 'count'}).sort_values(by = 'name', ascending=False).replace({True: 'Круглосуточные', False: 'Не круглосуточные'})
display(sample)
| is_24/7 | name | |
|---|---|---|
| 0 | Не круглосуточные | 1339 |
| 1 | Круглосуточные | 59 |
# строим круговую диаграмму распределения количества круглосуточных и не круглосуточных кофеен
fig = go.Figure(data=[go.Pie(labels=sample['is_24/7'], values=sample['name'])])
fig.update_layout(title="Распределение количества круглосуточных и не круглосуточных кофеен")
fig.show()
Подавляющие большинство кофеен не круглосуточные.
# выводим таблицу с средним рейтингом кофеен по районам
sample = coffee_house.groupby('district', as_index=False).agg({'rating':'mean'}).sort_values(by = 'rating', ascending=False)
sample
| district | rating | |
|---|---|---|
| 5 | Центральный административный округ | 4.336449 |
| 4 | Северо-Западный административный округ | 4.325806 |
| 2 | Северный административный округ | 4.291710 |
| 7 | Юго-Западный административный округ | 4.283333 |
| 0 | Восточный административный округ | 4.282857 |
| 8 | Южный административный округ | 4.232824 |
| 6 | Юго-Восточный административный округ | 4.225843 |
| 3 | Северо-Восточный административный округ | 4.216981 |
| 1 | Западный административный округ | 4.195333 |
# импортируем карту и хороплет
from folium import Map, Choropleth
# загружаем JSON-файл с границами округов Москвы
state_geo = '/datasets/admin_level_geomap.geojson'
# moscow_lat - широта центра Москвы, moscow_lng - долгота центра Москвы
moscow_lat, moscow_lng = 55.751244, 37.618423
# создаём карту Москвы
m = Map(location=[moscow_lat, moscow_lng], zoom_start=10)
# создаём хороплет с помощью конструктора Choropleth и добавляем его на карту
Choropleth(
geo_data=state_geo,
data=sample,
columns=['district', 'rating'],
key_on='feature.name',
fill_color='YlGn',
fill_opacity=0.8,
legend_name='Рейтинг кофеен по районам',
).add_to(m)
# выводим карту
m
Кофейни с самым выкоким рейтингом находятся в Центральном административном округе, а самый низкий рейтинг у кофеен в в Западном административном округе.
# выводим таблици со средней ценой одной чашки капучно по районам
coffee_house_cup = coffee_house.groupby('district', as_index=False).agg({'middle_coffee_cup': 'median'}).sort_values(by = 'middle_coffee_cup', ascending=False)
coffee_house_cup
| district | middle_coffee_cup | |
|---|---|---|
| 7 | Юго-Западный административный округ | 198.0 |
| 5 | Центральный административный округ | 190.0 |
| 1 | Западный административный округ | 189.0 |
| 4 | Северо-Западный административный округ | 165.0 |
| 3 | Северо-Восточный административный округ | 162.5 |
| 2 | Северный административный округ | 159.0 |
| 8 | Южный административный округ | 150.0 |
| 6 | Юго-Восточный административный округ | 147.5 |
| 0 | Восточный административный округ | 135.0 |
Построим фоновую картограмму (хороплет) со медианной ценой одной чашки капучино в заведениях каждого района
# импортируем карту и хороплет
from folium import Map, Choropleth
# загружаем JSON-файл с границами округов Москвы
state_geo = '/datasets/admin_level_geomap.geojson'
# moscow_lat - широта центра Москвы, moscow_lng - долгота центра Москвы
moscow_lat, moscow_lng = 55.751244, 37.618423
# создаём карту Москвы
m = Map(location=[moscow_lat, moscow_lng], zoom_start=10)
# создаём хороплет с помощью конструктора Choropleth и добавляем его на карту
Choropleth(
geo_data=state_geo,
data=coffee_house_cup,
columns=['district', 'middle_coffee_cup'],
key_on='feature.name',
fill_color='YlGn',
fill_opacity=0.8,
legend_name='Медианная средняя цена одной чашки капучино в заведениях по районам',
).add_to(m)
# выводим карту
m
Самая высокая медианная цена одной чашки капучино в заведениях в Юго-Западном и Центральном административных окруах, самая маленькая в Юго-Восточном и Восточном административных округах.
При открытии коффейни стоит ориентироваться на среднюю стоимость чашки в Центральном округе, там больше всего кофеен. А цена за чашку кофе не такая высока, как в Юго-Западном округе.
# сгрупперуем данные по расстоянию до центра москвы и медианной средней цене чашки капучино
sample = df.groupby('distance_center_km', as_index=False).agg({'middle_coffee_cup': 'median'})
sample
| distance_center_km | middle_coffee_cup | |
|---|---|---|
| 0 | 0 | NaN |
| 1 | 1 | 230.0 |
| 2 | 2 | 225.0 |
| 3 | 3 | 185.0 |
| 4 | 4 | 170.0 |
| 5 | 5 | 157.0 |
| 6 | 6 | 152.5 |
| 7 | 7 | 175.0 |
| 8 | 8 | 177.5 |
| 9 | 9 | 160.0 |
| 10 | 10 | 155.0 |
| 11 | 11 | 155.0 |
| 12 | 12 | 155.0 |
| 13 | 13 | 145.0 |
| 14 | 14 | 125.0 |
| 15 | 15 | 165.0 |
| 16 | 16 | 135.0 |
| 17 | 17 | 130.0 |
| 18 | 18 | 205.0 |
| 19 | 19 | 228.0 |
| 20 | 20 | NaN |
# выводим диаграмму зависимости между средней стоимостью одной чашки капучино и расстоянием до центра
fig, ax = plt.subplots()
ax = sns.regplot(x='distance_center_km', y='middle_coffee_cup', data=sample)
ax.set_title('Зависимость между средней стоимостью одной чашки капучино и расстоянием до центра')
ax.set_xlabel('Расстояние, км')
ax.set_ylabel('Средний цена')
plt.show()
# выводим коэффициент корреляции пирсона для более точной оценки
print(sample.corr())
distance_center_km middle_coffee_cup distance_center_km 1.000000 -0.304608 middle_coffee_cup -0.304608 1.000000
Заметной корреляции между средней ценой одной чашки капучино и удаленностью от центра нет.
Мы проанализировали кофейни Москвы из датасета составленного на основе данных сервисов Яндекс Карты и Яндекс Бизнес на лето 2022 года. Выявили количество кофеен и их распределение по округам, а также средний рейтинг и средную цену одной чашки капучино по разным округам.
В нашем датасете содержится информация о 1413 кофеен. Большинство из них расположены в Центральном административном округе, в то время как наименьшее количество кофеен находится в Северо-Западном административном округе.
В основном, кофейни не работают круглосуточно.
Лучшие кофейни по рейтингу также располагаются в Центральном административном округе, в то время как самые низкооцененные кофейни находятся в Западном административном округе.
Медианная стоимость чашки капучино самая высокая в Юго-Западном и Центральном административных округах, а самая низкая - в Юго-Восточном и Восточном административных округах. Однако стоимость чашки капучино не зависит от удаленности кофейни от центра города.
Открывая новое заведение, стоит ориентироваться на среднюю цену чашки кофе для Центрального административного округа, которая составляет 190 рублей, там больше всего кофеен, а цена за чашку кофе не такая высокая, как в Юго-Западном округе. Это позволит вам быть конкурентоспособными на рынке и привлечь целевую аудиторию, готовую тратить средства на качественный кофе и атмосферу.
Рекомендация для открытия нового заведения
На основе предоставленных данных, мы рекомендуем открыть кофейню в Центральном административном округе Москвы. Этот район имеет наибольшее количество кофеен, что указывает на высокий спрос и поток посетителей. К тому же, средний рейтинг кофеен в этом районе самый высокий, что говорит о качестве предлагаемых услуг и удовлетворенности клиентов.
Однако, учтите, что конкуренция в Центральном административном округе может быть выше, поэтому важно предложить уникальную концепцию и высокое качество услуг, чтобы выделиться среди конкурентов. Мы рекомендуем рассмотреть возможность проведения дополнительного исследования рынка и анализа конкурентов, чтобы принять обоснованное решение.
Мы рекомендуем провести:
Анализ конкурентов: Изучите существующие кофейни в Центральном административном округе, их концепции, меню, цены, рейтинги и отзывы клиентов. Определите их сильные и слабые стороны, а также возможные ниши, которые вы можете занять.
Определение целевой аудитории: Определите, кто является вашими потенциальными клиентами (возраст, пол, социальный статус, предпочтения и т.д.). Это поможет вам лучше понять их потребности и предложить продукты и услуги, которые будут востребованы.
Изучение трендов рынка: Определите актуальные тренды в индустрии кофеен, такие как популярные напитки, дизайн интерьера, маркетинговые акции и т.д. Это позволит вам быть в курсе последних новинок и предложить клиентам актуальные и интересные решения.
Проведение таких исследований поможет вам принять обоснованное решение о том, где и как открыть кофейню, а также определить оптимальную стратегию для ее развития и успеха на рынке.
Анализ особенностей рынка общественного питания Москвы
Мы проанализировали датасет с заведениями общественного питания Москвы, составленный на основе данных сервисов Яндекс Карты и Яндекс Бизнес на лето 2022 года. Выявили наиболее распостраненные категории заведений, количество посадочных мест на одно заведение, самые большие сети и их распределение по округам, а также средний рейтинг, средний чек по разным округам.
В Москве наиболее распространенными категориями заведений общественного питания являются кафе, рестораны кофейни, которые составляют почти 70% всех заведений.
Рестораны, бары/пабы и кофейни имеют наибольшее количество посадочных мест на одно заведение, около 80 мест, в то время как в пиццериях и булочных мест меньше, около 50.
Самая большая сеть в Москве - "Шоколадница" с более чем 120 заведениями.
Большинство заведений находится в Центральном адмистративном округе, при этом большинство из них являются ресторанами, кафе, кофейнями и барами.
Самый высокий средний рейтинг заведений находится в Северо-Восточном административном округе, а самый низкий - в Восточном административном округе.
Самый высокий средний чек находится в Центральном и Западном административных округах, а самый низкий - в Юго-Восточном, Южном и Северо-Восточном административных округах.
Чем ближе заведение к центру города, тем больше средний чек.
Анализ кофеен
Дополнительно мы проанализировали кофейни Москвы. Выявили количество кофеен и их распределение по округам, а также средний рейтинг и средную цену одной чашки капучино по разным округам.
Большинство из них расположены в Центральном административном округе, в то время как наименьшее количество кофеен находится в Северо-Западном административном округе. В основном, кофейни не работают круглосуточно.
Лучшие кофейни по рейтингу также располагаются в Центральном административном округе, в то время как самые низкооцененные кофейни находятся в Западном административном округе.
Медианная стоимость чашки капучино самая высокая в Юго-Западном и Центральном административных округах, а самая низкая - в Юго-Восточном и Восточном административных округах. Однако стоимость чашки капучино не зависит от удаленности кофейни от центра города.
Рекомендация для открытия нового заведения
На основе предоставленных данных, мы рекомендуем открыть кофейню в Центральном административном округе Москвы. Этот район имеет наибольшее количество кофеен, что указывает на высокий спрос и поток посетителей. К тому же, средний рейтинг кофеен в этом районе самый высокий, что говорит о качестве предлагаемых услуг и удовлетворенности клиентов.
Что касается стоимости чашки капучино, ориентируйтесь на среднюю цену для Центрального административного округа, которая составляет 190 рублей. Это позволит вам быть конкурентоспособными на рынке и привлечь целевую аудиторию, готовую тратить средства на качественный кофе и атмосферу.